#include "stdafx.h"

#include "MGCfg.h"
#include "MGListImpl.h"
#include "TUtils.h"

CGestureCommand::CGestureCommand()
 : GESTURE_BASE(), cbExtra(0), pbExtra(NULL)
{

}

CGestureCommand::CGestureCommand(const CMGPattern &rPattern, WORD wID2, LPCTSTR pszID2, LPCTSTR pszDesc2, BYTE bCatID2/* = 0*/)
 : GESTURE_BASE(), cbExtra(0), pbExtra(NULL)
{
	ASSERT(pszID2);
	ASSERT(pszDesc2);

	pattern = rPattern;
	wID = wID2;
	bCatID = bCatID2;

	int nID	= _tcslen(pszID2);
	int nDescription = _tcslen(pszDesc2);

	cbExtra = (USHORT)((nID + nDescription + 2) * sizeof(TCHAR));
	pbExtra = (LPBYTE)new BYTE[cbExtra];

	pszID = (LPTSTR)pbExtra;
	pszDesc = pszID + (nID + 1);
	
	_tcsncpy(pszID, pszID2, nID + 1);
	_tcsncpy(pszDesc, pszDesc2, nDescription + 1);
}

CGestureCommand::CGestureCommand(LPCTSTR pszPattern2, WORD wID2, LPCTSTR pszID2, LPCTSTR pszDesc2, BYTE bCatID2/* = 0*/)
{
	ASSERT(pszPattern2);
	ASSERT(pszID2);
	ASSERT(pszDesc2);

	pattern.SetFromString(pszPattern2);
	wID = wID2;
	bCatID = bCatID2;

	int nID	= _tcslen(pszID2);
	int nDescription = _tcslen(pszDesc2);

	cbExtra = (USHORT)((nID + nDescription + 2) * sizeof(TCHAR));
	pbExtra = (LPBYTE)new BYTE[cbExtra];

	pszID = (LPTSTR)pbExtra;
	pszDesc = pszID + (nID + 1);
	
	_tcsncpy(pszID, pszID2, nID + 1);
	_tcsncpy(pszDesc, pszDesc2, nDescription + 1);
}

CGestureCommand::~CGestureCommand()
{
	pattern.clear();
	delete [] pbExtra;
}

CGestureCommand::CGestureCommand(const CGestureCommand &rRhs)
 : GESTURE_BASE(), cbExtra(0), pbExtra(NULL)
{
	*this = rRhs;
}

CGestureCommand &CGestureCommand::operator =(const CGestureCommand &rRhs)
{
	if(this == &rRhs) return *this;

	pattern = rRhs.pattern;
	wID = rRhs.wID;
	bCatID = rRhs.bCatID;

	cbExtra = rRhs.cbExtra;
	delete [] pbExtra;
	pbExtra = (LPBYTE)new BYTE[cbExtra];
	::CopyMemory(pbExtra, rRhs.pbExtra, cbExtra);

	pszID = (LPTSTR)pbExtra;
	pszDesc = (LPTSTR)( pbExtra + ((LPBYTE)rRhs.pszDesc - rRhs.pbExtra) );

	return *this;
}

#include <iomanip>

/// 0, KEY_WHEEL_UP
/// 1, KEY_WHEEL_DOWN
/// 2, KEY_BUTTON1_UP (L Button)
/// 3, KEY_BUTTON2_UP (M or R Button)
const TCHAR CMGListImpl::tchKey[] = { _T('f'), _T('b'), _T('1'), _T('2'), };

/// 0, ArrowRight
/// 1, ArrowUp
/// 2, ArrowLeft
/// 3, ArrowDown

const TCHAR CMGListImpl::tchArrow[] = { _T('R'), _T('U'), _T('L'), _T('D'), };
 
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CMGListImpl::CMGListImpl() : m_nIndexFoundCached(-1)
{
	AUTO_MUTEX(CMGListImpl);
}

CMGListImpl::~CMGListImpl()
{
	AUTO_MUTEX(CMGListImpl);

	Empty();
}

int CMGListImpl::Add(LPCTSTR pszPattern, WORD wID, LPCTSTR pszID, LPCTSTR pszDesc, BYTE /*bCatTarget*/, BYTE bCatID)
{
	AUTO_MUTEX(CMGListImpl);

	int nID	= _tcslen(pszID);
	int nDescription = _tcslen(pszDesc);
	
	WORD cbExtra = (USHORT)((nID + nDescription + 2) * sizeof(TCHAR));

	MGLIST::iterator itFound = NULL; 
	int nFound = FindInsertAfter(pszPattern, itFound);

	LPGESTURE pGestureTemp = (nFound > -1) ? (*itFound) : NULL;
	if(itFound != m_list.end() && pGestureTemp->pattern.GetLength() == _tcslen(pszPattern) && !pGestureTemp->pattern.compare(pszPattern) )
	{
		// override the existing gesture command info.
		pGestureTemp->wID = wID;
		pGestureTemp->bCatID = bCatID;
		if (pGestureTemp->cbExtra < cbExtra)
		{
			delete[] pGestureTemp->pbExtra;
			pGestureTemp->cbExtra = cbExtra;
			pGestureTemp->pbExtra = (LPBYTE)new BYTE[cbExtra];
		}
	}
	else
	{
		pGestureTemp = (LPGESTURE)new GESTURE;

		pGestureTemp->pattern.SetFromString(pszPattern);
		pGestureTemp->wID = wID;
		pGestureTemp->bCatID = bCatID;
		pGestureTemp->cbExtra = cbExtra;
		pGestureTemp->pbExtra = (LPBYTE)new BYTE[cbExtra];
		
		m_list.insert(itFound, pGestureTemp);
	}

	pGestureTemp->pszID = (LPTSTR)pGestureTemp->pbExtra;
	pGestureTemp->pszDesc = pGestureTemp->pszID + (nID + 1);
	
	_tcsncpy(pGestureTemp->pszID, pszID, nID + 1);
	_tcsncpy(pGestureTemp->pszDesc, pszDesc, nDescription + 1);

	return nFound;
}

int CMGListImpl::Add(LPGESTURE pGesture, BYTE /*bCatTarget*/)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itFound = NULL; 
	int nFound = FindInsertAfter(pGesture->pattern.GetAsString().c_str(), itFound);

	LPGESTURE pGestureTemp = (nFound > -1) ? (*itFound) : NULL;
	if( itFound != m_list.end() && pGestureTemp->pattern.GetLength() == pGesture->pattern.GetLength() && pGestureTemp->pattern.equal(pGesture->pattern) )
	{
		// override the existing gesture command info.
		*pGestureTemp = *pGesture;
	}
	else
	{
		pGestureTemp = (LPGESTURE)new GESTURE(*pGesture);
		m_list.insert(itFound, pGestureTemp);
	}

	return nFound;
}

void CMGListImpl::Cleanup()
{
	AUTO_MUTEX(CMGListImpl);

	Empty();
}

int CMGListImpl::GetCount(BYTE /*bCatTarget*/)
{
	AUTO_MUTEX(CMGListImpl);

	return m_list.size();
}

LPGESTURE CMGListImpl::Get(int nIndex, BYTE /*bCatTarget*/)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itList = m_list.begin();
	MGLIST::iterator itEnd = m_list.end();

	int nIt = 0;
	while(nIt != nIndex)
	{
		itList++;
		nIt++;
		if(itList == itEnd) return NULL;
	}

	return (*itList);
}

int CMGListImpl::Find(LPCTSTR pszPattern, BYTE /*bCatTarget*/)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itFound;
	return Find(pszPattern, itFound);
}

int CMGListImpl::Find(LPCTSTR pszPattern, std::list<LPGESTURE>::iterator &it)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itBegin = m_list.begin();
	MGLIST::iterator itEnd = m_list.end();
	MGLIST::iterator itSorter = itBegin;

	int nBegin = 0;
	int nEnd = m_list.size();

	int nSorter = nBegin;
	int nSteps = nEnd;

	// lookup the cached index first
	if(m_nIndexFoundCached >= nBegin && m_nIndexFoundCached < nEnd)
	{
		itSorter = itBegin, nSorter = nBegin;
		for(int i = 0; i < m_nIndexFoundCached; i++)
			itSorter++, nSorter++;

		//	int nCompare = _tcsicmp(pszPattern, (*itSorter)->pszPattern);
		int nCompare = (*itSorter)->pattern.compare(pszPattern, FALSE);
		if(nCompare < 0)
		{
			itBegin = itSorter, nBegin = nSorter;
			nSteps = nEnd - nSorter;
		}
		else if(nCompare > 0)
		{
			itEnd = itSorter, nEnd = nSorter;
			nSteps = nSorter;
		}
		else if(nCompare == 0)
		{
			it = itSorter;
			return m_nIndexFoundCached;
		}
	}
	
	// binary search
	// reference - Binary Sorting Into a std::list
	// http://www.codeproject.com/vcpp/stl/binarysortstdlist.asp
	while(itBegin != itEnd)
	{
		itSorter = itBegin, nSorter = nBegin;

		nSteps = (int)(nSteps / 2);
		for(int i = 0; i < nSteps; i++)
			itSorter++, nSorter++;

		//	int nCompare = _tcsicmp(pszPattern, (*itSorter)->pszPattern);
		int nCompare = (*itSorter)->pattern.compare(pszPattern, FALSE);
		if(nCompare < 0)
		{
			itBegin = itSorter, nBegin = nSorter;

			if(nSteps == 0 && itBegin != itEnd)
			{
				itBegin++, nBegin++;
			}
		}
		else if(nCompare > 0)
		{
			itEnd = itSorter, nEnd = nSorter;
			if(nSteps == 0 && itEnd != itBegin)
			{
				itEnd--, nEnd--;
			}
		}
		else
		{
			it = itSorter;
			return (m_nIndexFoundCached = nSorter);
		}
	}

	m_nIndexFoundCached = nSorter;
	return -1;
}

int CMGListImpl::Find(WORD wID, BYTE /*bCatID*/)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itList = m_list.begin();
	MGLIST::iterator itEnd = m_list.end();

	int nIt = 0;
	if (m_nIndexFoundCached >= 0)
	{
		while(nIt != m_nIndexFoundCached)
		{
			itList++, nIt++;
			if(itList == itEnd) break;
		}
		
		if(itList != itEnd)
		{
			if(*itList)
			{
				if ((*itList)->wID == wID)
					return m_nIndexFoundCached;
			}
		}
	}

	itList = m_list.begin(), nIt = 0;

	while(itList != itEnd)
	{
		if (*itList)
		{
			if ((*itList)->wID == wID)
				return (m_nIndexFoundCached = nIt);
		}
		itList++, nIt++;
	}

	// not found
	return (m_nIndexFoundCached = -1);
}

int CMGListImpl::FindInsertAfter(LPCTSTR pszPattern, MGLIST::iterator &it)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itBegin = m_list.begin();
	MGLIST::iterator itEnd = m_list.end();
	MGLIST::iterator itSorter = itBegin;

	int nBegin = 0;
	int nEnd = m_list.size();

	int nSorter = nBegin;
	int nSteps = nEnd;

	// look up the cached index first
	if(m_nIndexFoundCached >= nBegin && m_nIndexFoundCached < nEnd)
	{
		itSorter = itBegin, nSorter = nBegin;
		for(int i = 0; i < m_nIndexFoundCached; i++)
			itSorter++, nSorter++;

		//	int nCompare = _tcsicmp(pszPattern, (*itSorter)->pszPattern);
		int nCompare = (*itSorter)->pattern.compare(pszPattern, FALSE);
		if(nCompare < 0)
		{
			itBegin = itSorter, nBegin = nSorter;
			nSteps = nEnd - nSorter;
		}
		else if(nCompare > 0)
		{
			itEnd = itSorter, nEnd = nSorter;
			nSteps = nSorter;
		}
		else if(nCompare == 0)
		{
			it = itSorter;
			return m_nIndexFoundCached;
		}
	}

	// binary search
	// reference - Binary Sorting Into a std::list
	// http://www.codeproject.com/vcpp/stl/binarysortstdlist.asp
	while(itBegin != itEnd)
	{
		itSorter = itBegin, nSorter = nBegin;

		nSteps = (int)(nSteps / 2);
		for(int i = 0; i < nSteps; i++)
			itSorter++, nSorter++;

		int nCompare = (*itSorter)->pattern.compare(pszPattern, FALSE);
		if(nCompare < 0)
		{
			itBegin = itSorter, nBegin = nSorter;

			if(nSteps == 0 && itBegin != itEnd)
			{
				itBegin++, nBegin++;
			}
		}
		else if(nCompare > 0)
		{
			itEnd = itSorter, nEnd = nSorter;
			if(nSteps == 0 && itEnd != itBegin)
			{
				itEnd--, nEnd--;
			}
		}
		else if(nCompare == 0)
		{
			itBegin = itEnd = itSorter, nBegin = nEnd = nSorter;
		}
	}

	itSorter = itEnd, nSorter = nEnd;

	it = itSorter;
	return (m_nIndexFoundCached = nSorter);
}

/**
 *	CMGListImpl::Remove()
 *
 *	@param LPCTSTR pszPattern
 *
 *	@return BOOL
 *
 */
BOOL CMGListImpl::Remove(LPCTSTR pszPattern, BYTE /*bCatTarget*/)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itFound;
	int nFound = Find(pszPattern, itFound);
	if (nFound >= 0)
	{
		if(*itFound)
		{
			delete (*itFound);
			(*itFound) = NULL;

			m_list.erase(itFound);
		}

		return TRUE;
	}

	return FALSE;
}

/**
 *	CMGListImpl::Remove()
 *
 *	@param int nIndex
 *
 *	@return BOOL
 *
 */
BOOL CMGListImpl::Remove(int nIndex, BYTE /*bCatTarget*/)
{
	AUTO_MUTEX(CMGListImpl);

	MGLIST::iterator itList = m_list.begin();
	MGLIST::iterator itEnd = m_list.end();

	int nIt = 0;
	while(nIt != nIndex)
	{
		itList++, nIt++;
		if(itList == itEnd) return FALSE;
	}

	if(*itList)
	{
		delete (*itList);
		(*itList) = NULL;

		m_list.erase(itList);

		return TRUE;
	}

	return FALSE;
}

/**
 *	CMGListImpl::Empty()
 *
 *
 *	@return void
 *
 */
void CMGListImpl::Empty()
{
	AUTO_MUTEX(CMGListImpl);

	std::for_each<MGLIST::iterator>(m_list.begin(), m_list.end(), MGLIST::Delete_t());
	m_list.clear();
}

/**
 *	CMGListImpl::GetAsString()
 *
 *	@param int nIndex
 *
 *	@return std::tstring
 *
 */
std::tstring CMGListImpl::GetAsString(int nIndex, BYTE bCatTarget)
{
	AUTO_MUTEX(CMGListImpl);

	std::tostringstream tss;
	LPGESTURE pGesture = Get(nIndex, bCatTarget);
	if (pGesture)
		tss << pGesture->pattern.GetAsString().c_str() << _T("\\")
		<< pGesture->pszID << _T("\\")
		<< pGesture->wID << _T("\\")
		<< pGesture->pszDesc << _T("\\")
		<< (UINT)pGesture->bCatID;	// cast bCatID to UINT explicitly to avoid it from being treated as char in non-unicode build

	return tss.str().c_str();
}

/**
 *	CMGListImpl::AddFromString()
 *
 *	@param LPCTSTR pszString
 *
 *	@return int
 *
 */
int CMGListImpl::AddFromString(LPCTSTR pszString)
{
	AUTO_MUTEX(CMGListImpl);

	int nIndex = -1;
	std::tstring strTemp, strPattern, strID, strDescription;
	WORD wID;
	BYTE bCatID;
	if (!tutil::ExtractSubStringT<TCHAR>()(strPattern, pszString, 0, _T('\\')))
		return nIndex;
	if (!tutil::ExtractSubStringT<TCHAR>()(strID, pszString, 1, _T('\\')))
		return nIndex;
	if (!tutil::ExtractSubStringT<TCHAR>()(strTemp, pszString, 2, _T('\\')))
		return nIndex;
	wID = (WORD)_ttoi(strTemp.c_str());
	if (!tutil::ExtractSubStringT<TCHAR>()(strDescription, pszString, 3, _T('\\')))
		return nIndex;
	if (!tutil::ExtractSubStringT<TCHAR>()(strTemp, pszString, 4, _T('\\')))
		return nIndex;
	bCatID = (BYTE)_ttoi(strTemp.c_str());
	
	nIndex = Add(strPattern.c_str(), wID, strID.c_str(), strDescription.c_str(), 0, bCatID);
	
	return nIndex;
}

void CMGListImpl::UpdateRegistry(BOOL bSaveAndValidate)
{
	AUTO_MUTEX(CMGListImpl);

	regdword rgdwNumberOfGesture(SECTION _T("Gestures\\") _T("Number of Gestures"), HKEY_CURRENT_USER);

	//	TCHAR szNumber[4] = { NULL };
	if (bSaveAndValidate)
	{
		// write to the registry
		int nGestureCount = (int)rgdwNumberOfGesture;
		for (int i = GetCount(); i < nGestureCount; i++)
		{
			std::tostringstream tss;
			tss << SECTION << _T("Gestures\\") << std::setfill( _T('0') ) << std::setw(3) << i;
			//	_stprintf(szNumber, _T("%03d"), i);
			//	tss << SECTION << _T("Gestures\\") << szNumber;
			regtstring rgstrPattern(tss.str().c_str(), HKEY_CURRENT_USER);
			rgstrPattern.remove_key();
		}
		
		nGestureCount = GetCount();
		rgdwNumberOfGesture = (DWORD)nGestureCount;
		for (i = 0; i < nGestureCount; i++)
		{
			std::tostringstream tss;
			//	_stprintf(szNumber, _T("%03d"), i);
			//	tss << SECTION << _T("Gestures\\") << szNumber;
			tss << SECTION << _T("Gestures\\") << std::setfill( _T('0') ) << std::setw(3) << i;
			regtstring rgstrPattern(tss.str().c_str(), HKEY_CURRENT_USER);
			std::tstring sPattern = GetAsString(i, 0);
			rgstrPattern = sPattern;
		}
	}
	else
	{
		// read from the registry
		Empty();
		int nGestureCount = (int)rgdwNumberOfGesture;
		for (int i = 0; i < nGestureCount; i++)
		{
			std::tostringstream tss;
			//	_stprintf(szNumber, _T("%03d"), i);
			//	tss << SECTION << _T("Gestures\\") << szNumber;
			tss << SECTION << _T("Gestures\\") << std::setfill( _T('0') ) << std::setw(3) << i;
			regtstring rgstrPattern(tss.str().c_str(), HKEY_CURRENT_USER);
			AddFromString(((std::tstring)rgstrPattern).c_str());
		}
	}
}

BOOL CMGListImpl::Load(LPCTSTR pszFilePath)
{
	AUTO_MUTEX(CMGListImpl);

	std::tifstream file;
	file.exceptions( std::ifstream::eofbit | std::ifstream::failbit | std::ifstream::badbit );
	try
	{
		file.open(pszFilePath);
		if(!file.good())
		{
			::SetLastError(ERROR_INVALID_ACCESS);
			throw NULL;
		}

		std::tstring buf;

		std::getline(file, buf);
		if(_tcscmp( buf.c_str(), _T("[Mouse Gestures]") ) )
		{
			::SetLastError(ERROR_INVALID_DATA);
			throw NULL;
		}

		std::getline(file, buf);
		int nCount = _ttoi(buf.c_str());

		Empty();
		for(int i = 0; i < nCount; i++)
		{
			std::getline(file, buf);

			std::tstring strTemp;
			if (!tutil::ExtractSubString(strTemp, buf.c_str(), 1, _T('=')))
			{
				::SetLastError(ERROR_INVALID_DATA);
				throw NULL;
			}
			
			AddFromString(strTemp.c_str());
		}

		file.close();
	}
	catch(...)
	{
		if(file.good()) file.close();

		DWORD dwErr = ::GetLastError();

		std::tostringstream tss;
		tss << _T("File reading failed.") << std::endl;
		tss << pszFilePath << std::endl;
		tss << tutil::ErrorString(dwErr) << std::endl;

		::MessageBox(NULL, tss.str().c_str(), NULL, MB_OK | MB_ICONERROR);

		return FALSE;
	}

	return TRUE;
}

/**
 *	CMGListImpl::Save()
 *
 *	@param LPCTSTR pszFilePath
 *
 *	@return BOOL
 *
 */
BOOL CMGListImpl::Save(LPCTSTR pszFilePath)
{
	AUTO_MUTEX(CMGListImpl);

	std::tofstream file;
	file.exceptions( std::ifstream::eofbit | std::ifstream::failbit | std::ifstream::badbit );
	try
	{
		file.open(pszFilePath);
		if(!file.good())
		{
			::SetLastError(ERROR_INVALID_ACCESS);
			throw NULL;
		}
		file << _T("[Mouse Gestures]") << std::endl;
		int nCount = GetCount();
		file << nCount << std::endl;
		for(int i = 0; i < nCount; i++)
			file << std::setfill( _T('0') ) << std::setw(3) << i << _T("=") << GetAsString(i, 0) << std::endl;

		file.close();
	}
	catch(...)
	{
		if(file.good()) file.close();

		DWORD dwErr = ::GetLastError();

		std::tostringstream tss;
		tss << pszFilePath << std::endl;
		tss << tutil::ErrorString(dwErr) << std::endl;

		::MessageBox(NULL, tss.str().c_str(), NULL, MB_OK | MB_ICONERROR);

		return FALSE;
	}

	return TRUE;
}

WORD CMGListImpl::GetMouseCmd(LPCTSTR pszKey, BYTE bCatTarget)
{
	AUTO_MUTEX(CMGListImpl);

	TRACE(_T("MouseOption: Key(%s)\n"), pszKey);
	
	WORD wID = 0;
	
	int nFound = Find(pszKey, bCatTarget);
	if (nFound >= 0)
	{
		wID = Get(nFound, bCatTarget)->wID;
	}
	else
		wID = (WORD) - 1;
	
	return wID;
};

/**
 *	CMGListImpl::GetCommandDesc()
 *
 *	@param WORD wCommand
 *
 *	@return std::tstring
 *
 */
std::tstring CMGListImpl::GetCommandDesc(WORD wCommand)
{
	AUTO_MUTEX(CMGListImpl);

	LPGESTURE pGesture = Get(Find(wCommand), 0);
	if (pGesture)
	{
		LPCTSTR pszDesc = ::lstrlen(pGesture->pszDesc) ? pGesture->pszDesc : pGesture->pszID;
		std::tostringstream tss;
		tss << _T("(") << pszDesc << _T(")");
		return tss.str().c_str();
	}
	
	return std::tstring();
}

/**
 *	CMGListImpl::GetCommandID()
 *
 *	@param WORD wCommand
 *
 *	@return std::tstring
 *
 */
std::tstring CMGListImpl::GetCommandID(WORD wCommand)
{
	AUTO_MUTEX(CMGListImpl);

	LPGESTURE pGesture = Get(Find(wCommand), 0);
	if (pGesture)
		return pGesture->pszID;
	
	return std::tstring();
}

BYTE CMGListImpl::GetCommandCat(WORD wCommand)
{
	AUTO_MUTEX(CMGListImpl);

	LPGESTURE pGesture = Get(Find(wCommand), 0);
	if (pGesture)
		return pGesture->bCatID;
	
	return 0;
}

TCHAR CMGListImpl::GetKeyMark(enum MG::KeyDef key) const
{
	AUTO_MUTEX(CMGListImpl);

	return CMGListImpl::tchKey[key];
}

TCHAR CMGListImpl::GetArrowMark(enum MG::ArrowDef arrow) const
{
	AUTO_MUTEX(CMGListImpl);

	return CMGListImpl::tchArrow[arrow];
}
